<template>
	<div
		:class="classes()"
		:style="style"
		@click="clickFn"
		@wheel="wheelFn"
		@mouseenter="hover=true"
		@mouseleave="hover=false"
	>
		<slot
			v-if="prefixIcon||$slots.prefix"
			name="prefix"
			v-bind="slotScope()"
		>
			<Icon
				:name="prefixIcon"
				class="pf-NumberInput_icon pf--prefix"
				@click.stop="clickPrefixIconFn"
			/>
		</slot>
		<div :class="wrapClasses()">
			<input
				ref="inputRef"
				v-bind="$attrs"
				type="text"
				:value="showValue()"
				:disabled="disabled"
				:readonly="readonly"
				class="pf-NumberInput_input"
				@input="inputFn"
				@focus="focusFn"
				@blur="blurFn"
				@keydown="keydownFn"
			>
			<template v-if="!showValue()">{{placeholder}}</template>
		</div>
		<Icon
			v-if="showClearIcon()"
			name="bi-x-circle-fill"
			class="pf-NumberInput_clear"
			@click.stop
			@mousedown.left="clearFn"
		/>
		<slot
			v-if="suffixIcon||$slots.suffix"
			name="suffix"
			v-bind="slotScope()"
		>
			<Icon
				:name="suffixIcon"
				class="pf-NumberInput_icon pf--suffix"
				@click.stop="clickSuffixIconFn"
			/>
		</slot>
		<div
			v-if="showControl()"
			class="pf-NumberInput_control"
		>
			<Icon
				name="bi-chevron-up"
				:class="stepIconClasses('max')"
				@click.stop="clickStepIconFn(1)"
			/>
			<Icon
				name="bi-chevron-down"
				:class="stepIconClasses('min')"
				@click.stop="clickStepIconFn(-1)"
			/>
		</div>
	</div>
</template>

<script setup lang="ts">
import type {
	NumberInputValue,
	NumberInputShape,
} from './type';
import {
	customRef,
	shallowRef,
	computed,
	nextTick,
	watchEffect,
} from 'vue';
import Icon from '../Icon/Icon';
import {numberRound,numberAdd,numberMulti,numberIsFinite,throttle} from '../../../util';
import useAutoFocus from '../../../composables/useAutoFocus';
/**
 * 数字输入框组件
 * @author 唐国雄
 * @date 2022/11/9
 * 属性----------------------
 * v-model			当前值
 * :shape			形状
 * :placeholder		占位文本
 * :prefix-icon		前缀图标
 * :suffix-icon		后缀图标
 * :min				最小值
 * :max				最大值
 * :step			步长
 * :precision		精度
 * :allow-empty		是否允许空值
 * :lazy			懒校验
 * :hide-button		隐藏按钮
 * :disabled		禁用状态
 * :readonly		只读状态
 * :clearable		可清空
 * :autofocus		自动聚焦
 * 事件----------------------
 * @update:modelValue	值变化
 * @clear				点击清空
 * @click				点击事件
 * @click-prefix-icon	前缀图标, 点击事件
 * @click-suffix-icon	后缀图标, 点击事件
 * 插槽----------------------
 * #prefix			前缀插槽
 * #suffix			后缀插槽
 * 方法----------------------
 * clear()				清空
 * adjustByStep(count)	改变指定步长的数值
 */
if(typeof cache==='function'){
	cache=cache();
}
const {allowKeys,ctrlAllowKeys}=cache;

const emit=defineEmits<{
	(e:'update:modelValue',val:NumberInputValue):void;
	(e:'clear'):void;
	(e:'click'):void;
	(e:'click-prefix-icon'):void;
	(e:'click-suffix-icon'):void;
}>();
const props=withDefaults(defineProps<{
	class?:any;
	style?:any;
	modelValue:NumberInputValue;
	shape?:NumberInputShape;
	placeholder?:string;
	prefixIcon?:string;
	suffixIcon?:string;
	min?:number;
	max?:number;
	step?:number|null;
	precision?:number;
	allowEmpty?:boolean;
	lazy?:boolean;
	hideButton?:boolean|null;
	disabled?:boolean;
	readonly?:boolean;
	clearable?:boolean;
	autofocus?:boolean;
}>(),{
	step:1,
	hideButton:null,
});
const inputRef=shallowRef<HTMLInputElement>(null);
const focus=shallowRef(false);
const hover=shallowRef(false);
//region 输入框内部值
const innerValue=customRef((track,trigger) => {
	let val='';
	const obj={
		get value():string{
			track();
			return val;
		},
		set value(str:string){
			if(val!==str){
				val=str;
				trigger();
			}
		},
		set(num:number){
			if(num!==Number(val)){
				val=num+'';
				trigger();
			}
		},
		trigger,
	};
	return {
		get(){
			return obj;
		},
		set(){},
	};
}).value;
//endregion
//自动聚焦
useAutoFocus(() => props.autofocus,inputRef,focus);
//region 显示值
const showValue=() => {
	return focus.value
		?innerValue.value
		:props.modelValue;
};
//endregion
//region 是否显示, 清空按钮
const showClearIcon=(():boolean => {
	return props.clearable&&hover.value&&showValue()!==''&&!(props.disabled||props.readonly);
});
//endregion
//region 是否显示, 控制按钮
const showControl=(():boolean => {
	return realStep()!=null&&!(props.disabled||props.readonly)&&(
		props.hideButton==null
			?hover.value
			:!props.hideButton
	);
});
//endregion
//region 实际步长
const realStep=(():number|null => {
	const {step,precision}=props;
	if(!step){
		return null;
	}
	if(precision==null){
		return step;
	}
	return numberRound(step,precision);
});
//endregion
//region 空值隐含代表的数字
const emptyNumber=computed<number>(() => {
	return getValue(0);
});
//endregion
//region 类名
const classes=() => {
	return 'pf-NumberInput'+
		(props.shape?` pf--shape-${props.shape}`:'')+
		(focus.value?' pf--focus':'')+
		(props.disabled?' pf--disabled':props.readonly?' pf--readonly':'')+
		(props.class?' '+props.class:'');
};
const wrapClasses=() => {
	return 'pf-NumberInput_inputWrap'+
		(showValue()?'':' pf--empty');
};
//endregion
//region 步进图标类名
const stepIconClasses=(type:'max'|'min') => {
	const {
		[type]:limit,
		modelValue:value,
	}=props;
	return 'pf-NumberInput_stepIcon'+
		(
			props.disabled||props.readonly||
			(limit!==undefined&&value!==''&&(type==='max'
				?numberAdd(value,realStep())>limit
				:numberAdd(value,-realStep())<limit
			))
				?' pf--disabled'
				:' pf--enable'
		);
};
//endregion
//region 插槽作用域
const slotScope=() => {
	return {
		withFocus,
		clear,
		adjustByStep,
	};
};
//endregion
//region 聚焦逻辑
function withFocus(after?:() => void):boolean{
	if(props.disabled){
		return;
	}
	after&&after();
	if(!focus.value){
		nextTick(() => {
			inputRef.value.focus();
		});
	}
}
//endregion
//region 清空操作
let clearFocus=false;
function clear(focus:boolean):void{
	clearFocus=focus;
	emit('clear');
	if(props.modelValue!==''){
		if(props.allowEmpty){
			emit('update:modelValue','');
		}else if(props.modelValue!==emptyNumber.value){
			emit('update:modelValue',emptyNumber.value);
		}
	}
}
function clearFn(){
	clear(true);
	withFocus();
}
//endregion
//region 值变化处理器
function inputFn(e:InputEvent){
	let inputVal=(e.target as HTMLInputElement).value;
	if(inputVal===''){
		const val=getValue('');
		if(props.modelValue!==val){
			emit('update:modelValue',val);
		}
		nextTick(() => {
			innerValue.value='';
		});
	}else{
		let val=Number(inputVal);
		if(numberIsFinite(val)){
			const {min,max,precision}=props;
			if(precision!=null){
				const pos=inputVal.indexOf('.')+1;
				if(pos>0&&inputVal.length-pos>precision){
					//超出精度, 重置
					innerValue.trigger();
					return;
				}
			}
			let limit=false;
			if(min!==undefined&&val<min){
				val=min;
				limit=true;
			}
			if(max!==undefined&&val>max){
				val=max;
				limit=true;
			}
			if(limit&&!props.lazy){
				inputVal=val+'';
			}
			if(props.modelValue!==val){
				emit('update:modelValue',val);
			}
			//处理无效的前缀0
			const noPrefixZero=inputVal.replace(/^(-)?0+(?!\.)(.+)$/,'$1$2');
			if(innerValue.value!==noPrefixZero){
				nextTick(() => {
					innerValue.value=noPrefixZero;
				});
			}else{
				//输入框含有无效的前缀0, 重置
				innerValue.trigger();
			}
		}else if(inputVal==='-'){
			innerValue.value='-';
		}else{
			//错误输入, 重置
			innerValue.trigger();
		}
	}
}
//endregion
//region 键盘控制
function keydownFn(e:KeyboardEvent){
	let {key,ctrlKey}=e;
	key=key.toLowerCase();
	if(key==='arrowup'){
		adjustByStep(1);
	}else if(key==='arrowdown'){
		adjustByStep(-1);
	}
	if(
		!(
			ctrlAllowKeys.has(key)
				?ctrlKey
				:allowKeys.has(key)
		)||
		(key==='.'&&props.precision===0)||
		(key==='-'&&props.min>=0)
	){
		e.preventDefault();
	}
}
//endregion
//region 滚轮控制
const throttleAdjustByStep=throttle(adjustByStep);
function wheelFn(e:WheelEvent){
	if(focus.value){
		const {deltaY}=e;
		if(deltaY>0){
			throttleAdjustByStep(-1);
		}else if(deltaY<0){
			throttleAdjustByStep(1);
		}
		e.preventDefault();
	}
}
//endregion
//region 改变指定步长的数值
function adjustByStep(count:number):boolean{
	let step=realStep();
	if(props.disabled||props.readonly||step==null||!count){
		return false;
	}
	const {modelValue:value,min,max}=props;
	const factor=count>0?1:-1;
	count=Math.abs(count);
	if(count>1){
		step=numberMulti(step,count);
	}
	const val=numberAdd(value===''?emptyNumber.value:value,factor*step);
	const minLimit=min!==undefined&&val<min;
	const maxLimit=max!==undefined&&val>max;
	if(minLimit||maxLimit){
		innerValue.value=value+'';
		if(value===''){
			emit('update:modelValue',emptyNumber.value);
			return true;
		}
		return false;
	}
	emit('update:modelValue',val);
	return true;
}
//endregion
//region 点击事件
function clickStepIconFn(count:number){
	withFocus(() => {
		adjustByStep(count);
	});
}
function clickFn(){
	withFocus(() => {
		emit('click');
	});
}
function clickPrefixIconFn(){
	withFocus(() => {
		emit('click-prefix-icon');
	});
}
function clickSuffixIconFn(){
	withFocus(() => {
		emit('click-suffix-icon');
	});
}
//endregion
//region 聚焦,失焦处理器
function focusFn(){
	innerValue.value=props.modelValue+'';
	focus.value=true;
}
function blurFn(){
	focus.value=false;
	if(clearFocus){
		clearFocus=false;
		withFocus();
	}
}
//endregion
//region 获取限制后的值
function getValue(val:NumberInputValue){
	if(val===''){
		if(props.allowEmpty){
			return '';
		}
		return emptyNumber.value;
	}else{
		const {min,max,precision}=props;
		if(min!==undefined&&val<min){
			val=min;
		}
		if(max!==undefined&&val>max){
			val=max;
		}
		if(precision!=null){
			val=numberRound(val,precision);
		}
		return val;
	}
}
//endregion
watchEffect(() => {
	const value=props.modelValue;
	const val=getValue(value);
	if(value!==val){
		emit('update:modelValue',val);
	}else{
		innerValue.set(val);
	}
});
//region 导出的内容
defineExpose({
	clear,
	adjustByStep,
});
//endregion
</script>

<script lang="ts">
//region 类型声明
interface Cache{
	allowKeys:Set<string>;
	ctrlAllowKeys:Set<string>;
}
//endregion
//region 全局缓存
let cache:Cache|(() => Cache)=function(){
	return {
		allowKeys:new Set([
			'1','2','3','4','5','6','7','8','9','0',
			'.','-',
			'arrowleft','arrowright',
			'backspace','delete',
			'tab','f5',
		]),
		ctrlAllowKeys:new Set(['a','c','v']),
	};
};
//endregion
export default {
	inheritAttrs:false,
};
</script>